#pragma once
#ifndef __GLF_DEBUGGER_TWEAKER_H__
#define __GLF_DEBUGGER_TWEAKER_H__

#include <map>
#include <vector>
#include <string>

#include <glf/config.h>
#include <glf/io/stream.h>
#include <glf/debugger/debugger.h>
#include <glf/debugger/xml.h>


#define GLF_TWEAKABLE(_var)								RegisterVariable(#_var, _var);
#define GLF_TWEAKABLE_VALUE(_var, _value)				RegisterVariable(#_var, _var); _var = _value;
#define GLF_TWEAKABLE_RANGE(_var, _range)				RegisterVariable(#_var, _var); SetRangeDesc(#_var, _range, "");
#define GLF_TWEAKABLE_DESC(_var, _desc)					RegisterVariable(#_var, _var); SetRangeDesc(#_var, "", _desc);
#define GLF_TWEAKABLE_RANGEDESC(_var, _range, _desc)	RegisterVariable(#_var, _var); SetRangeDesc(#_var, _range, _desc);

#undef TYPE_BOOL

#ifdef GetClassName
	#undef GetClassName
#endif

namespace glf
{
namespace debugger
{

class Debugger;
class XmlReader;
class XmlWriter;

class Tweakable
{
public:
	enum Type
	{
		TYPE_UNKNOWN,
		TYPE_INT,
		TYPE_FLOAT,
		TYPE_STRING,
		TYPE_BOOL,
		TYPE_COLOR,
		TYPE_COLORF,
		TYPE_VECTOR2D,
		TYPE_VECTOR3D,
		TYPE_VECTOR4D
	};

	struct Color
	{
		Color() {}
		Color(const std::string& val);
		uchar r; uchar g; uchar b; uchar a;
	};
	struct Colorf
	{
		Colorf() {}
		Colorf(const std::string& val);
		float r; float g; float b; float a;
	};
	struct Vector2d
	{
		Vector2d() {}
		Vector2d(const std::string& val);
		float x; float y;
	};
	struct Vector3d
	{
		Vector3d() {}
		Vector3d(const std::string& val);
		float x; float y; float z;
	};
	struct Vector4d
	{
		Vector4d() {}
		Vector4d(const std::string& val);
		float x; float y; float z; float w;
	};

	Tweakable();
	virtual ~Tweakable();

	const char* GetClassName() const
	{
		return mClassName.c_str();
	}
	Type GetType(const std::string& id);

	virtual const char* GetInstanceName() const;

	virtual void WriteValues(std::string& xml);

	void LoadXML(const char* xmlFileName, bool setValues = true);
	void LoadXML(IOStream& xmlFile, bool setValues = true);
	void SaveXML(const char* xmlFileName);

	void SetPersistent();

private:

	void SetValuesFromXML(const std::string& xml, bool setValues = true);
	std::string GetPersistentName();
	void LoadPersistent();
	void SavePersistent();

public:

	virtual void OnSetValue(const std::string& name);

	void SetClassName(const char* name)
	{
		GLF_ASSERT(mClassName.size() == 0);
		mClassName = name;
	}

	void RegisterVariable(const char* name, int& var)
	{
		RegisterVariableName(name);
		GetCurrentGroup()->mMappings[name] = Mapping(TYPE_INT, &var);
	}

	void RegisterVariable(const char* name, float& var)
	{
		RegisterVariableName(name);
		GetCurrentGroup()->mMappings[name] = Mapping(TYPE_FLOAT, &var);
	}

	void RegisterVariable(const char* name, std::string& var)
	{
		RegisterVariableName(name);
		GetCurrentGroup()->mMappings[name] = Mapping(TYPE_STRING, &var);
	}

	void RegisterVariable(const char* name, bool& var)
	{
		RegisterVariableName(name);
		GetCurrentGroup()->mMappings[name] = Mapping(TYPE_BOOL, &var);
	}

	void RegisterVariable(const char* name, Color& var)
	{
		RegisterVariableName(name);
		GetCurrentGroup()->mMappings[name] = Mapping(TYPE_COLOR, &var);
	}

	void RegisterVariable(const char* name, Colorf& var)
	{
		RegisterVariableName(name);
		GetCurrentGroup()->mMappings[name] = Mapping(TYPE_COLORF, &var);
	}

	void RegisterVariable(const char* name, Vector2d& var)
	{
		RegisterVariableName(name);
		GetCurrentGroup()->mMappings[name] = Mapping(TYPE_VECTOR2D, &var);
	}

	void RegisterVariable(const char* name, Vector3d& var)
	{
		RegisterVariableName(name);
		GetCurrentGroup()->mMappings[name] = Mapping(TYPE_VECTOR3D, &var);
	}

	void RegisterVariable(const char* name, Vector4d& var)
	{
		RegisterVariableName(name);
		GetCurrentGroup()->mMappings[name] = Mapping(TYPE_VECTOR4D, &var);
	}

	void SetRangeDesc(const char* name, const char* range, const char* desc)
	{
		std::map<std::string, Mapping>::iterator it = GetCurrentGroup()->mMappings.find(name);
		if(it != GetCurrentGroup()->mMappings.end())
		{
			it->second.mRange = range;
			it->second.mDesc = desc;
		}
	}

	void SetRange(const char* name, const char* range)
	{
		std::map<std::string, Mapping>::iterator it = GetCurrentGroup()->mMappings.find(name);
		if(it != GetCurrentGroup()->mMappings.end())
		{
			it->second.mRange = range;
		}
	}

	void SetDesc(const char* name, const char* desc)
	{
		std::map<std::string, Mapping>::iterator it = GetCurrentGroup()->mMappings.find(name);
		if(it != GetCurrentGroup()->mMappings.end())
		{
			it->second.mDesc = desc;
		}
	}

	void PushGroup(const char* name)
	{
		GLF_ASSERT(mCurrentGroup != NULL);
		
		bool found = false;
		std::vector<Group*>::iterator it = mCurrentGroup->mSubGroups.begin();
		for (; !found && it != mCurrentGroup->mSubGroups.end(); ++it)
		{
			if ((*it)->mName == name)
			{
				mCurrentGroup = *it;
				found = true;
			}
		}
		//create a group if no group with same name exists
		if (!found)
		{
			Group* group = new Group();
			group->mParentGroup = mCurrentGroup;
			group->mName = name;
			mCurrentGroup->mSubGroups.push_back(group);
			mCurrentGroup = group;
		}
	}

	void PopGroup()
	{
		mCurrentGroup = GetCurrentGroup()->mParentGroup;
		GLF_ASSERT(mCurrentGroup != NULL);
	}

	void ClearGroups()
	{
		mBaseGroup.Clear();
		mCurrentGroup = &mBaseGroup;
	}

	//delete a tweaker by name in the current group.
	void UnregisterVariable(const char* name)
	{
		if (mCurrentGroup->mMappings.find(name) != mCurrentGroup->mMappings.end())
		{
			mCurrentGroup->mMappings.erase(mCurrentGroup->mMappings.find(name));
			//unregister variable too.
			std::vector<std::string>::iterator it = mCurrentGroup->mVariableNames.begin();
			for (; it != mCurrentGroup->mVariableNames.end(); ++it)
			{
				if (*it==name)
				{
					mCurrentGroup->mVariableNames.erase(it);
					break;
				}
			}
		}
		//delete the group if we just deleted the last tweaker
		if (mCurrentGroup != &mBaseGroup && mCurrentGroup->mVariableNames.empty())
		{
			mCurrentGroup->Clear();
			std::string name(mCurrentGroup->mName);
			mCurrentGroup = mCurrentGroup->mParentGroup;
			std::vector<Group*>::iterator it = mCurrentGroup->mSubGroups.begin();
			for (; it != mCurrentGroup->mSubGroups.end(); ++it)
			{
				if ((*it)->mName == name)
				{
					mCurrentGroup->mSubGroups.erase(it);
					break;
				}
			}
		}
	}

	//clear a specific group by path.
	void ClearGroup(std::vector<std::string> path)
	{
		if (SelectGroup(path))
		{
			mCurrentGroup->Clear();
		}
	}

	//return the path of the current group (you need the group path to unregister its tweakers)
	std::vector<std::string> GetCurrentGroupPath() const 
	{
		std::vector<std::string> path;
		Group* currGroup = mCurrentGroup;
		while (currGroup != NULL)
		{
			path.push_back(currGroup->mName);
			currGroup = currGroup->mParentGroup;
		}

		std::vector<std::string> reverse;
		for(int i = path.size() - 2; i >= 0; i--) //omit last entry (baseGroup)
		{
			reverse.push_back(path[i]);
		}
		return reverse;
	}

	//select a group by path. Return true if the group was found
	bool SelectGroup(const std::vector<std::string>& path)
	{
		if (path.empty())
		{
			mCurrentGroup = &mBaseGroup;
			return true;
		}
		else
		{
			Group* currGroup = &mBaseGroup;
			bool found = false;
			std::vector<std::string>::const_iterator it_path = path.begin();
			std::vector<std::string>::const_iterator end_path = path.end();

			for (; it_path != end_path && currGroup != NULL; ++it_path)
			{
				found = false;
				std::vector<Group*>::const_iterator it_group = currGroup->mSubGroups.begin();
				std::vector<Group*>::const_iterator end = currGroup->mSubGroups.end();
				
				for (; !found && it_group != end; ++it_group)
				{
					if ((*it_group)->mName == (*it_path))
					{
						found = true;
						currGroup = *it_group;
					}
				}
				if (!found)
					break;
			}
			//if the group was found, select it
			if (found)
				mCurrentGroup = currGroup;
			return found;
		}
	}

	void AddColumn()
	{
		char buffer[32];
		Sprintf_s(buffer, "column_%d", GetCurrentGroup()->mVariableNames.size());
		RegisterVariable(buffer, mColumnVar);
	}

	void SendValues();

	struct Mapping
	{
		Mapping(Type type, void* address) :
			mType(type),
			mAddress(address)
		{
			// ensure readable address
			uchar value = *(uchar*)address;
			value = value;
		}

		Mapping() :
			mType(TYPE_UNKNOWN),
			mAddress(NULL)
		{
		}

		Type mType;
		void* mAddress;
		std::string mRange;
		std::string mDesc;
	};

	struct Group
	{
		Group()
			: mParentGroup(NULL)
		{
		}

		void Clear()
		{
			for(size_t i = 0; i < mSubGroups.size(); i++)
			{
				delete mSubGroups[i];
			}
			mMappings.clear();
			mVariableNames.clear();
			mSubGroups.clear();
		}

		~Group()
		{
			Clear();
		}

		std::string mName;
		std::map<std::string, Mapping> mMappings;
		std::vector<std::string> mVariableNames;
		std::vector<Group*> mSubGroups;
		Group* mParentGroup;
	};

private:
	void SetValue(Group& group, Type type, const std::string& name, const std::string& value);

	void WriteGroup(Group& group, XmlWriter& writer, bool writeTag);
	
	Group* GetCurrentGroup() const
	{
		GLF_ASSERT(mCurrentGroup != NULL);
		return mCurrentGroup;
	}

	void RegisterVariableName(const char* name)
	{
		if(GetCurrentGroup()->mMappings.find(name) == GetCurrentGroup()->mMappings.end())
		{
			GetCurrentGroup()->mVariableNames.push_back(name);
		}
	}

	Group mBaseGroup;
	Group* mCurrentGroup;
	std::string mClassName;
	std::vector<char> mXml;
	int mColumnVar;
	bool mPersistent;

	friend class Debugger;
	friend class Tweakers;
	friend class XmlHandler;
};

#if GLF_ENABLE_DEBUGGER

class Tweakers : public Module
{
	GLF_DECLARE_SINGLETON_WITH_DEP(Tweakers, Debugger)
public:
	virtual ~Tweakers();

	virtual void UpdateFrame();
	virtual void Parse(int type, PacketReader& in);
	virtual void ConnectionClosed();

private:
	Tweakers();

	enum MessageType
	{
		// Debugger to Glf
		MESSAGE_REQUEST_INSTANCES,
		MESSAGE_REQUEST_VALUES,
		MESSAGE_SET_VALUE,

		// Glf to Debugger
		MESSAGE_ADD_INSTANCE,
		MESSAGE_REMOVE_INSTANCE,
		MESSAGE_VALUES
	};

	void RegisterTweakable(Tweakable* tweakable);
	void UnregisterTweakable(Tweakable* tweakable);
	void SendTweakables();
	void SendValues(Tweakable* tweakable);

	std::map<Tweakable*, bool> mTweakables;
	std::map<Tweakable*, bool> mAddedTweakables;
	std::map<Tweakable*, bool> mRemovedTweakables;

	friend class Tweakable;
};

#endif // GLF_ENABLE_DEBUGGER

} // end namespace debugger
} // end namespace glf

#endif //__GLF_DEBUGGER_TWEAKER_H__
